package util

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"gitee.com/extrame/fb-runner/store"
	"gitee.com/extrame/fb-runner/version"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

var Config = new(config)
var execDir = ""

var NoLocalHost = errors.New("本机无对应节点")

type config struct {
	Ca            bool
	MaxRetry      int    `default:"5"`
	CliDelay      int    `default:"3"`
	Database      string `default:"leveldb"`
	Source        string `default:".."`
	Base          string `default:"."`
	Persist       bool   `default:"false"`
	Version       string `default:"2.3"`
	CaVersion     string `default:"1.5.0"`
	NetwokName    string `default:"net"`
	Domain        string
	LogFile       string
	Organizations map[string]*Organization
	defaultPeer   *Organization
	Hosts         map[string]*Host
	dockerVersion version.Version
	caVersion     version.Version
}

func (c *config) IsMaster(hostid string) bool {
	for _, host := range c.Hosts {
		if host.Id == hostid && host.IsMaster {
			return true
		}
	}
	return false
}

func (c *config) SetVersion(dv version.Version, cv version.Version) {
	c.dockerVersion = dv
	c.caVersion = cv
}

func (c *config) GetCv() version.Version {
	return c.caVersion
}

func (c *config) GetDv() version.Version {
	return c.dockerVersion
}

func (c *config) BaseIsNotExists() bool {
	_, err := os.Stat(c.GetBase())
	if os.IsNotExist(err) {
		return true
	}
	return false
}

func (c *config) HasCaInThisMachine() bool {
	for _, org := range c.Organizations {
		if org.Ca.IsLocal() {
			return true
		}
	}
	return false
}

type Host struct {
	Ip       string
	Id       string
	IsMaster bool
}

func (h *Host) GetIP() string {
	if h.Id == store.GetLocalId() {
		return "localhost"
	}
	return h.Ip
}

var defaultDomain = "example.com"
var defaultOrganizations = map[string]*Organization{
	"ordererOrg": {
		IsOrderer: true,
		Msp:       "OrdererMSP",
		Ca: &Peer{
			Host: "master",
			Port: 11054,
		},
		Peers: map[string]*Peer{
			"peer0": {
				Host: "master",
				Port: 7050,
			},
		},
	},
	"org1": {
		Msp: "Org1MSP",
		Ca: &Peer{
			Host: "master",
			Port: 7054,
		},
		Peers: map[string]*Peer{
			"peer0": {
				Host: "master",
				Port: 7051,
			},
		},
	},
	"org2": {
		Msp: "Org2MSP",
		Ca: &Peer{
			Host: "master",
			Port: 9054,
		},
		Peers: map[string]*Peer{
			"peer0": {
				Host: "master",
				Port: 9051,
			},
		},
	},
}

type OrganizationWithBase struct {
	Organization
	RealBase string
	Base     string
	Channel  string
}

func (owb *OrganizationWithBase) GetCaPath() string {
	return owb.Organization.GetCaPath(owb.Base)
}

func (owb *OrganizationWithBase) GetCaPem(prefix string) string {
	return owb.Organization.GetCaPem(owb.RealBase, prefix)
}

func (owb *OrganizationWithBase) GetTlsCaPath(p *Peer) string {
	return p.GetTlsCaPath(owb.Base)
}

func (owb *OrganizationWithBase) OtherOrg() []*OrganizationWithBase {
	return []*OrganizationWithBase{}
}

func (owb *OrganizationWithBase) GetTlsCaPem(p *Peer, prefix string) string {
	return p.GetTlsCaPem(owb.RealBase, prefix)
}

func (owb *OrganizationWithBase) GetUserCertStr(username string) string {
	return owb.Organization.GetUserCertStr(owb.RealBase, username)
}
func (owb *OrganizationWithBase) GetUserKeyStr(username string) string {
	return owb.Organization.GetUserKeyStr(owb.RealBase, username)
}

type Organization struct {
	hosts     map[string]*Host
	Peers     map[string]*Peer
	Ca        *Peer
	IsOrderer bool
	Msp       string
	key       string
	domain    string
}

func (o *Organization) GetUserCertStr(base, username string) string {
	var signcerts = filepath.Join(base, "organizations", o.GetParentDir(), o.GetHost()+"/users/"+username+"@"+o.GetHost()+"/msp/signcerts/cert.pem")
	file, err := os.Open(signcerts)
	if err != nil {
		logrus.Fatal("出现错误，", err)
	} else {
		var bts []byte
		bts, err = ioutil.ReadAll(file)
		if err != nil {
			logrus.Fatal("出现错误，", err)
		}
		return strings.ReplaceAll(string(bts), "\n", "\\n")
	}
	return ""
}

func (o *Organization) GetUserKeyStr(base, username string) string {
	var key = filepath.Join(base, "organizations", o.GetParentDir(), o.GetHost()+"/users/"+username+"@"+o.GetHost()+"/msp/keystore/*")
	fileKey := MatchedFile(key)
	logrus.Info("读取", fileKey, key)
	file, err := os.Open(fileKey)
	if err != nil {
		logrus.Fatal("出现错误，", err)
	} else {
		var bts []byte
		bts, err = ioutil.ReadAll(file)
		if err != nil {
			logrus.Fatal("出现错误，", err)
		}
		return strings.ReplaceAll(string(bts), "\n", "\\n")
	}
	return ""
}

func (o *Organization) GetAllowdIPs() []string {
	var ips = make(map[string]bool)
	for _, p := range o.Peers {
		if p.host.Id == store.GetLocalId() {
			ips["localhost"] = true
		}
		ips[p.host.Ip] = true
	}
	ips[o.Ca.host.Ip] = true
	var result = make([]string, len(ips))
	var i = 0
	for k, _ := range ips {
		result[i] = k
		i++
	}
	return result
}

func (o *Organization) GetDomain() string {
	return o.domain
}

func (o *Organization) GetPeersDir() string {
	if o.IsOrderer {
		return "orderers"
	}
	return "peers"
}

func (o *Organization) GetName() string {
	if o.key != "" {
		return strings.ToUpper(o.key[:1]) + o.key[1:]
	}
	return ""
}

func (o *Organization) GetCaHostAndPortStr() string {
	if o.Ca == nil {
		logrus.Fatalf("%s没有配置Ca，请补充相关配置", o.key)
	}
	return fmt.Sprintf("%s:%d", o.Ca.GetHost(), o.Ca.Port)
}

func (o *Organization) GetCaPath(base string) string {
	return filepath.Join(base, "organizations", o.GetParentDir(), o.GetHost(), "/ca/ca."+o.GetHost()+"-cert.pem")
}

func (o *Organization) GetCaPem(base string, prefix string) string {
	fName := o.GetCaPath(base)
	file, err := os.Open(fName)
	if err != nil {
		logrus.Error("GetCaPem error:", err)
		return ""
	}
	var bts []byte
	bts, err = ioutil.ReadAll(file)
	if err != nil {
		logrus.Fatal("GetCaPem error:", err)
	}
	return strings.ReplaceAll(string(bts), "\n", "\n"+prefix)
}

func (o *Organization) GetConnArgs(base string) []string {
	p, _ := o.GetLocalPeer(true)
	return []string{"-o",
		p.GetUrl(), "--ordererTLSHostnameOverride", p.GetHost(), "--tls", "true",
		"--cafile", filepath.Join(base, "organizations", o.GetParentDir(), o.GetHost()+"/tlsca/tlsca."+o.GetHost()+"-cert.pem"),
	}
}

// func (o *Organization) GetPortStr() string {
// 	return strconv.Itoa(o.Port)
// }

func (o *Organization) GetHost() string {
	if o.domain == "-" {
		return o.key
	}
	return o.key + "." + o.domain
}

func (o *Organization) GetParentDir() string {
	if o.IsOrderer {
		return "ordererOrganizations"
	} else {
		return "peerOrganizations"
	}
}

func (o *Organization) GetCaUrl() string {
	return o.Ca.GetUrl()
}

func (o *Organization) GetKey() string {
	return o.key
}

func (o *Organization) GetMspPath(base string) string {
	return filepath.Join(base, "organizations", o.GetParentDir(), fmt.Sprintf(o.GetHost()+"/users/Admin@"+o.GetHost()+"/msp"))
}

type Peer struct {
	host *Host
	Host string
	// IP   string
	Port int
	key  string
	org  *Organization
}

func (p *Peer) IP() string {
	return p.host.Ip
}

func (p *Peer) IPPort() string {
	return fmt.Sprintf("%s:%d", p.host.Ip, p.Port)
}

func (p *Peer) GetTlsCaPath(base string) string {
	return filepath.Join(base, "/organizations/", p.org.GetParentDir(), fmt.Sprintf(p.org.GetHost()+"/peers/"+p.GetHost()+"/tls/ca.crt"))
}

func (p *Peer) GetTlsCaPem(base string, prefix string) string {
	fName := p.GetTlsCaPath(base)
	file, err := os.Open(fName)
	if err != nil {
		logrus.Error("GetTlsCaPem error:", err)
		return ""
	}
	var bts []byte
	bts, err = ioutil.ReadAll(file)
	if err != nil {
		logrus.Fatal("GetTlsCaPem error:", err)
	}
	return strings.ReplaceAll(string(bts), "\n", "\n"+prefix)
}

func (p *Peer) GetKey() string {
	return p.key
}

func (p *Peer) IsLocal() bool {
	return p.host.Id == store.GetLocalId()
}

func (p *Peer) GetUrl() string {
	return fmt.Sprintf("%s:%d", p.host.GetIP(), p.Port)
}

func (p *Peer) GetRemoteUrl() string {
	return fmt.Sprintf("%s:%d", p.host.Ip, p.Port)
}

//返回域名形式的host标识
func (p *Peer) GetHost() string {
	if p.org.domain == "-" {
		return fmt.Sprintf("%s.%s", p.key, p.org.key)
	}
	return fmt.Sprintf("%s.%s.%s", p.key, p.org.key, p.org.domain)
}

func (p *Peer) GetCcPort() int {
	return p.Port + 1
}

//remoteIsFallback 如果没有本机节点，选择远程节点
func (o *Organization) GetPeer() (*Peer, error) {
	return o.GetLocalPeer(true)
}

//remoteIsFallback 如果没有本机节点，选择远程节点
func (o *Organization) GetLocalPeer(remoteIsFallback ...bool) (*Peer, error) {
	var localId = store.GetLocalId()
	var other *Peer
	for _, p := range o.Peers {
		if p.host.Id == localId {
			return p, nil
		} else if other == nil {
			other = p
		}
	}
	if len(remoteIsFallback) > 0 && remoteIsFallback[0] && other != nil {
		return other, nil
	}

	return nil, NoLocalHost
}

//返回不在本机的列表，返回类似peer1.org1.example.com:xxx.xxx.xxx.xxx的形式，用于配置extra_hosts
func (o *Organization) GetRemoteDomains() map[string]string {
	var ips = make(map[string]string)
	for _, peer := range o.Peers {
		if peer.host.Id != store.GetLocalId() {
			ips[peer.GetHost()] = peer.host.GetIP()
		}
	}
	return ips
}

func (o *Organization) GetLocalPeers() []*Peer {
	var results = make([]*Peer, 0)
	var localId = store.GetLocalId()
	for _, p := range o.Peers {
		if p.host.Id == localId {
			results = append(results, p)
		}
	}
	return results
}

func (o *Organization) GetPeers() []*Peer {
	var results = make([]*Peer, 0)
	for _, p := range o.Peers {
		results = append(results, p)
	}
	return results
}

func (c *config) GetFullHost(o *Organization) string {
	return o.key + "." + c.Domain
}

func (c *config) Config() string {
	return filepath.Join(c.GetSource(), "config")
}

func (c *config) GetBase() string {
	base := filepath.Join(GetExecDir(), c.Base)
	base, _ = filepath.Abs(base)
	return base
}

func (c *config) GetSource() string {
	source := filepath.Join(GetExecDir(), c.Source)
	source, _ = filepath.Abs(source)
	return source
}

func (c *config) GetPeerOrganizations() map[string]*Organization {
	var results = make(map[string]*Organization)
	for k, v := range c.Organizations {
		if !v.IsOrderer {
			results[k] = v
		}
	}
	return results
}

func (c *config) GetPeerOrganization() *Organization {
	if c.defaultPeer != nil {
		return c.defaultPeer
	}
	for _, v := range c.Organizations {
		if !v.IsOrderer {
			c.defaultPeer = v
			return v
		}
	}
	logrus.Fatal("没有配置Peer机构")
	return nil
}

func (c *config) GetOrdererOrganization() *Organization {
	for _, v := range c.Organizations {
		if v.IsOrderer {
			return v
		}
	}
	logrus.Fatalln("没有配置Orderer机构，请设置至少一个参数IsOrderer为true的机构")
	return nil
}

//Init 初始化配置，如果需要重新写回，返回true，不需要则返回false
func (c *config) Init() bool {
	var changed = false
	if c.Domain == "" {
		logrus.Infoln("domain参数为空，初始化为默认参数" + defaultDomain)
		logrus.Infoln("如果不需要统一各机构域名后缀，domain请配置为-")
		c.Domain = defaultDomain
		changed = true
	}
	if c.Organizations == nil || len(c.Organizations) == 0 {
		logrus.Infoln("organizations参数为空，初始化为默认参数")
		c.Organizations = defaultOrganizations
		changed = true
	}

	for k, org := range c.Organizations {
		if host, ok := c.Hosts[org.Ca.Host]; ok {
			org.Ca.host = host
			org.Ca.org = org
			org.Ca.key = "ca"
		} else {
			logrus.Fatalln("没有这个配置的Host：", org.Ca.Host, "请检查配置文件中的Host配置")
		}
		org.key = k
		org.domain = c.Domain
		org.hosts = c.Hosts
		for k1, peer := range org.Peers {
			if host, ok := c.Hosts[peer.Host]; ok {
				peer.host = host
				peer.key = k1
				peer.org = org
			} else {
				logrus.Fatalln("没有这个配置的Host：", peer.Host, "请检查配置文件中的Host配置")
			}
		}
	}
	return changed
}

func (c *config) CheckSource() error {
	if c.Source == "" {
		return errors.New("配置模板路径（source配置项,默认应指向samples文件夹）未配置或配置错误，")
	}
	if c.Base == "" {
		return errors.New("目标配置保存路径（base配置项）未配置或配置错误，")
	}
	return nil
}

//获得配置的日志文件路径
func (c *config) GetLogFile() string {
	if c.LogFile == "" {
		c.LogFile = filepath.Join(c.GetBase(), "log-fabric-"+c.NetwokName)
	}
	MkdirFor(c.LogFile)
	return c.LogFile
}

func GetExecDir() string {
	if execDir == "" {
		ex, err := os.Executable()
		if err != nil {
			logrus.Fatalln(errors.Wrap(err, "获取执行文件路径"))
		}
		execDir = filepath.Dir(ex)
	}
	return execDir
}
